﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using LumenLiquid.Debug;
using LumenLiquid.InGame;
using Microsoft.Xna.Framework.Graphics;

namespace LumenLiquid.Resources
{
    public class Texturemanager
    {
        private struct STexture
        {
            public string Id { get; set; }
            public UTexture Texture { get; set; }

            [StructLayout(LayoutKind.Explicit)]
            internal struct UTexture
            {
                [FieldOffset(0)]
                private byte type;
                [FieldOffset(8)]
                private Texture texture;
                [FieldOffset(8)]
                private Texture2D texture2D;
                [FieldOffset(8)]
                private Texture3D texture3D;
                [FieldOffset(8)]
                private TextureCube textureCube;

                public UTexture(Texture texture)
                    : this()
                {
                    this.texture = texture;
                    type = 0;
                }

                public UTexture(Texture2D texture2D)
                    : this()
                {
                    this.texture2D = texture2D;
                    type = 1;
                }

                public UTexture(Texture3D texture3D)
                    : this()
                {
                    this.texture3D = texture3D;
                    type = 2;
                }

                public UTexture(TextureCube textureCube)
                    : this()
                {
                    this.textureCube = textureCube;
                    type = 3;
                }

                public Type TextureType()
                {
                    switch (type)
                    {
                        case 0:
                            return typeof(Texture);
                        case 1:
                            return typeof(Texture2D);
                        case 2:
                            return typeof(Texture3D);
                        case 3:
                            return typeof(TextureCube);
                    }

                    return null;
                }

                public object Texture
                {
                    get
                    {
                        switch (type)
                        {
                            case 0:
                                return texture;
                            case 1:
                                return texture2D;
                            case 2:
                                return texture3D;
                            case 3:
                                return textureCube;
                        }

                        return null;
                    }
                    set
                    {
                        if (value.GetType() == typeof(Texture))
                        {
                            type = 0;
                            texture = (Texture)value;
                        }
                        else if (value.GetType() == typeof(Texture2D))
                        {
                            type = 1;
                            texture2D = (Texture2D)value;
                        }
                        else if (value.GetType() == typeof(Texture3D))
                        {
                            type = 2;
                            texture3D = (Texture3D)value;
                        }
                        else if (value.GetType() == typeof(TextureCube))
                        {
                            type = 3;
                            textureCube = (TextureCube)value;
                        }

                    }
                }
            }

            public STexture(string id = null)
                : this()
            {
                Id = id;
            }
        }


        private Dictionary<string, List<STexture>> _textures;

        public Texturemanager()
        {
            DebugHandler.Log("Starting texturemanager...", DebugHandler.EDebugLevel.Code);
            _textures = new Dictionary<string, List<STexture>>();
            _textures.Add("global", new List<STexture>());
        }

        #region GetTexture
        /// <summary>
        /// Gets a texture in the global group
        /// </summary>
        /// <param name="id">The id of the texture to load</param>
        /// <returns>The texture file</returns>
        /// <remarks>If texture doesn't exist, the texture will be loaded. The global group cannot be unloaded, so watch out what you put in there.</remarks>
        public Texture GetGlobalTexture(string id)
        {
            DebugHandler.Log("Getting global texture with id \"" + id + "\"");

            STexture texture = _textures["global"].Find(m => Equals(m.Id, id));

            if (texture.Id == null)
            {
                texture.Texture = new STexture.UTexture(Main.Instance.Content.Load<Texture>(Path.Content.Textures + id));
                texture.Id = id;
            }

            if (texture.Texture.Texture == null)
            {
                DebugHandler.Log("Texture with id \"" + id + "\" was not found.", DebugHandler.EDebugLevel.Warning);
                return null;
            }
            else if (texture.Texture.TextureType() != typeof(Texture))
            {
                DebugHandler.Log("Texture with id \"" + id + "\" is not of type Textue.", DebugHandler.EDebugLevel.Warning);
                return null;
            }

            _textures["global"].Add(texture);

            return (Texture)texture.Texture.Texture;
        }

        /// <summary>
        /// Gets a texture for the current world
        /// </summary>
        /// <param name="id">The id of the texture to load</param>
        /// <returns>The texture file</returns>
        /// <remarks>If texture doesn't exist, the texture will be loaded.</remarks>
        public Texture GetWorldTexture(string id)
        {
            DebugHandler.Log("Getting world texture with id \"" + id + "\"");

            if (!_textures.ContainsKey(MainGame.Instance.World))
                _textures.Add(MainGame.Instance.World, new List<STexture>());

            STexture texture = _textures[MainGame.Instance.World].Find(m => Equals(m.Id, id));

            if (texture.Id == null)
            {
                texture.Texture = new STexture.UTexture(Main.Instance.Content.Load<Texture>(Path.Content.WorldFolder + MainGame.Instance.World + Path.Content.MapTextures + id));
                texture.Id = id;
            }

            if (texture.Texture.Texture == null)
            {
                DebugHandler.Log("Texture with id \"" + id + "\" was not found.", DebugHandler.EDebugLevel.Warning);
                return null;
            }
            else if (texture.Texture.TextureType() != typeof(Texture))
            {
                DebugHandler.Log("Texture with id \"" + id + "\" is not of type Textue.", DebugHandler.EDebugLevel.Warning);
                return null;
            }

            _textures[MainGame.Instance.World].Add(texture);

            return (Texture)texture.Texture.Texture;
        }

        /// <summary>
        /// Gets a texture from a specific group
        /// </summary>
        /// <param name="id">The id of the texture</param>
        /// <param name="group">The group in which the texture is placed</param>
        /// <returns>The texture file</returns>
        /// <remarks>If texture doesn't exist, the texture will be loaded.</remarks>
        public Texture GetGroupTexture(string id, string group)
        {
            DebugHandler.Log("Getting texture with id \"" + id + "\"");

            if (!_textures.ContainsKey(group))
                _textures.Add(group, new List<STexture>());

            STexture texture = _textures[group].Find(m => Equals(m.Id, id));

            if (texture.Id == null)
            {
                texture.Texture = new STexture.UTexture(Main.Instance.Content.Load<Texture>(Path.Content.Textures + id));
                texture.Id = id;
            }

            if (texture.Texture.Texture == null)
            {
                DebugHandler.Log("Texture with id \"" + id + "\" was not found.", DebugHandler.EDebugLevel.Warning);
                return null;
            }
            else if (texture.Texture.TextureType() != typeof(Texture))
            {
                DebugHandler.Log("Texture with id \"" + id + "\" is not of type Textue.", DebugHandler.EDebugLevel.Warning);
                return null;
            }

            _textures[group].Add(texture);

            return (Texture)texture.Texture.Texture;
        }
        #endregion

        #region GetTexture2D
        /// <summary>
        /// Gets a texture in the global group
        /// </summary>
        /// <param name="id">The id of the texture to load</param>
        /// <returns>The texture file</returns>
        /// <remarks>If texture doesn't exist, the texture will be loaded. The global group cannot be unloaded, so watch out what you put in there.</remarks>
        public Texture2D GetGlobalTexture2D(string id)
        {
            DebugHandler.Log("Getting global texture with id \"" + id + "\"");

            STexture texture = _textures["global"].Find(m => Equals(m.Id, id));

            if (texture.Id == null)
            {
                texture.Texture = new STexture.UTexture(Main.Instance.Content.Load<Texture2D>(id));
                texture.Id = id;
            }

            if (texture.Texture.Texture == null)
            {
                DebugHandler.Log("Texture with id \"" + id + "\" was not found.", DebugHandler.EDebugLevel.Warning);
                return null;
            }
            else if (texture.Texture.TextureType() != typeof(Texture2D))
            {
                DebugHandler.Log("Texture with id \"" + id + "\" is not of type Textue.", DebugHandler.EDebugLevel.Warning);
                return null;
            }

            _textures["global"].Add(texture);

            return (Texture2D)texture.Texture.Texture;
        }

        /// <summary>
        /// Gets a texture for the current world
        /// </summary>
        /// <param name="id">The id of the texture to load</param>
        /// <returns>The texture file</returns>
        /// <remarks>If texture doesn't exist, the texture will be loaded.</remarks>
        public Texture2D GetWorldTexture2D(string id)
        {
            DebugHandler.Log("Getting world texture with id \"" + id + "\"");

            if (!_textures.ContainsKey(MainGame.Instance.World))
                _textures.Add(MainGame.Instance.World, new List<STexture>());

            STexture texture = _textures[MainGame.Instance.World].Find(m => Equals(m.Id, id));

            if (texture.Id == null)
            {
                texture.Texture = new STexture.UTexture(Main.Instance.Content.Load<Texture2D>(Path.Content.WorldFolder + MainGame.Instance.World + Path.Content.MapTextures + id));
                texture.Id = id;
            }

            if (texture.Texture.Texture == null)
            {
                DebugHandler.Log("Texture with id \"" + id + "\" was not found.", DebugHandler.EDebugLevel.Warning);
                return null;
            }
            else if (texture.Texture.TextureType() != typeof(Texture2D))
            {
                DebugHandler.Log("Texture with id \"" + id + "\" is not of type Textue.", DebugHandler.EDebugLevel.Warning);
                return null;
            }

            _textures[MainGame.Instance.World].Add(texture);

            return (Texture2D)texture.Texture.Texture;
        }

        /// <summary>
        /// Gets a texture from a specific group
        /// </summary>
        /// <param name="id">The id of the texture</param>
        /// <param name="group">The group in which the texture is placed</param>
        /// <returns>The texture file</returns>
        /// <remarks>If texture doesn't exist, the texture will be loaded.</remarks>
        public Texture2D GetGroupTexture2D(string id, string group)
        {
            DebugHandler.Log("Getting texture with id \"" + id + "\"");

            if (!_textures.ContainsKey(group))
                _textures.Add(group, new List<STexture>());

            STexture texture = _textures[group].Find(m => Equals(m.Id, id));

            if (texture.Id == null)
            {
                texture.Texture = new STexture.UTexture(Main.Instance.Content.Load<Texture2D>(Path.Content.Textures + id));
                texture.Id = id;
            }

            if (texture.Texture.Texture == null)
            {
                DebugHandler.Log("Texture with id \"" + id + "\" was not found.", DebugHandler.EDebugLevel.Warning);
                return null;
            }
            else if (texture.Texture.TextureType() != typeof(Texture2D))
            {
                DebugHandler.Log("Texture with id \"" + id + "\" is not of type Textue.", DebugHandler.EDebugLevel.Warning);
                return null;
            }

            _textures[group].Add(texture);

            return (Texture2D)texture.Texture.Texture;
        }
        #endregion

        #region GetTexture3D
        /// <summary>
        /// Gets a texture in the global group
        /// </summary>
        /// <param name="id">The id of the texture to load</param>
        /// <returns>The texture file</returns>
        /// <remarks>If texture doesn't exist, the texture will be loaded. The global group cannot be unloaded, so watch out what you put in there.</remarks>
        public Texture3D GetGlobalTexture3D(string id)
        {
            DebugHandler.Log("Getting global texture with id \"" + id + "\"");

            STexture texture = _textures["global"].Find(m => Equals(m.Id, id));

            if (texture.Id == null)
            {
                texture.Texture = new STexture.UTexture(Main.Instance.Content.Load<Texture3D>(Path.Content.Textures + id));
                texture.Id = id;
            }

            if (texture.Texture.Texture == null)
            {
                DebugHandler.Log("Texture with id \"" + id + "\" was not found.", DebugHandler.EDebugLevel.Warning);
                return null;
            }
            else if (texture.Texture.TextureType() != typeof(Texture3D))
            {
                DebugHandler.Log("Texture with id \"" + id + "\" is not of type Textue.", DebugHandler.EDebugLevel.Warning);
                return null;
            }

            _textures["global"].Add(texture);

            return (Texture3D)texture.Texture.Texture;
        }

        /// <summary>
        /// Gets a texture for the current world
        /// </summary>
        /// <param name="id">The id of the texture to load</param>
        /// <returns>The texture file</returns>
        /// <remarks>If texture doesn't exist, the texture will be loaded.</remarks>
        public Texture3D GetWorldTexture3D(string id)
        {
            DebugHandler.Log("Getting world texture with id \"" + id + "\"");

            if (!_textures.ContainsKey(MainGame.Instance.World))
                _textures.Add(MainGame.Instance.World, new List<STexture>());

            STexture texture = _textures[MainGame.Instance.World].Find(m => Equals(m.Id, id));

            if (texture.Id == null)
            {
                texture.Texture = new STexture.UTexture(Main.Instance.Content.Load<Texture3D>(Path.Content.WorldFolder + MainGame.Instance.World + Path.Content.MapTextures + id));
                texture.Id = id;
            }

            if (texture.Texture.Texture == null)
            {
                DebugHandler.Log("Texture with id \"" + id + "\" was not found.", DebugHandler.EDebugLevel.Warning);
                return null;
            }
            else if (texture.Texture.TextureType() != typeof(Texture3D))
            {
                DebugHandler.Log("Texture with id \"" + id + "\" is not of type Textue.", DebugHandler.EDebugLevel.Warning);
                return null;
            }

            _textures[MainGame.Instance.World].Add(texture);

            return (Texture3D)texture.Texture.Texture;
        }

        /// <summary>
        /// Gets a texture from a specific group
        /// </summary>
        /// <param name="id">The id of the texture</param>
        /// <param name="group">The group in which the texture is placed</param>
        /// <returns>The texture file</returns>
        /// <remarks>If texture doesn't exist, the texture will be loaded.</remarks>
        public Texture3D GetGroupTexture3D(string id, string group)
        {
            DebugHandler.Log("Getting texture with id \"" + id + "\"");

            if (!_textures.ContainsKey(group))
                _textures.Add(group, new List<STexture>());

            STexture texture = _textures[group].Find(m => Equals(m.Id, id));

            if (texture.Id == null)
            {
                texture.Texture = new STexture.UTexture(Main.Instance.Content.Load<Texture3D>(Path.Content.Textures + id));
                texture.Id = id;
            }

            if (texture.Texture.Texture == null)
            {
                DebugHandler.Log("Texture with id \"" + id + "\" was not found.", DebugHandler.EDebugLevel.Warning);
                return null;
            }
            else if (texture.Texture.TextureType() != typeof(Texture3D))
            {
                DebugHandler.Log("Texture with id \"" + id + "\" is not of type Textue.", DebugHandler.EDebugLevel.Warning);
                return null;
            }

            _textures[group].Add(texture);

            return (Texture3D)texture.Texture.Texture;
        }
        #endregion

        #region GetTextureCube
        /// <summary>
        /// Gets a texture in the global group
        /// </summary>
        /// <param name="id">The id of the texture to load</param>
        /// <returns>The texture file</returns>
        /// <remarks>If texture doesn't exist, the texture will be loaded. The global group cannot be unloaded, so watch out what you put in there.</remarks>
        public TextureCube GetGlobalTextureCube(string id)
        {
            DebugHandler.Log("Getting global texture with id \"" + id + "\"");

            STexture texture = _textures["global"].Find(m => Equals(m.Id, id));

            if (texture.Id == null)
            {
                texture.Texture = new STexture.UTexture(Main.Instance.Content.Load<TextureCube>(Path.Content.Textures + id));
                texture.Id = id;
            }

            if (texture.Texture.Texture == null)
            {
                DebugHandler.Log("Texture with id \"" + id + "\" was not found.", DebugHandler.EDebugLevel.Warning);
                return null;
            }
            else if (texture.Texture.TextureType() != typeof(TextureCube))
            {
                DebugHandler.Log("Texture with id \"" + id + "\" is not of type Textue.", DebugHandler.EDebugLevel.Warning);
                return null;
            }

            _textures["global"].Add(texture);

            return (TextureCube)texture.Texture.Texture;
        }

        /// <summary>
        /// Gets a texture for the current world
        /// </summary>
        /// <param name="id">The id of the texture to load</param>
        /// <returns>The texture file</returns>
        /// <remarks>If texture doesn't exist, the texture will be loaded.</remarks>
        public TextureCube GetWorldTextureCube(string id)
        {
            DebugHandler.Log("Getting world texture with id \"" + id + "\"");

            if (!_textures.ContainsKey(MainGame.Instance.World))
                _textures.Add(MainGame.Instance.World, new List<STexture>());

            STexture texture = _textures[MainGame.Instance.World].Find(m => Equals(m.Id, id));

            if (texture.Id == null)
            {
                texture.Texture = new STexture.UTexture(Main.Instance.Content.Load<TextureCube>(Path.Content.WorldFolder + MainGame.Instance.World + Path.Content.MapTextures + id));
                texture.Id = id;
            }

            if (texture.Texture.Texture == null)
            {
                DebugHandler.Log("Texture with id \"" + id + "\" was not found.", DebugHandler.EDebugLevel.Warning);
                return null;
            }
            else if (texture.Texture.TextureType() != typeof(TextureCube))
            {
                DebugHandler.Log("Texture with id \"" + id + "\" is not of type Textue.", DebugHandler.EDebugLevel.Warning);
                return null;
            }

            _textures[MainGame.Instance.World].Add(texture);

            return (TextureCube)texture.Texture.Texture;
        }

        /// <summary>
        /// Gets a texture from a specific group
        /// </summary>
        /// <param name="id">The id of the texture</param>
        /// <param name="group">The group in which the texture is placed</param>
        /// <returns>The texture file</returns>
        /// <remarks>If texture doesn't exist, the texture will be loaded.</remarks>
        public TextureCube GetGroupTextureCube(string id, string group)
        {
            DebugHandler.Log("Getting texture with id \"" + id + "\"");

            if (!_textures.ContainsKey(group))
                _textures.Add(group, new List<STexture>());

            STexture texture = _textures[group].Find(m => Equals(m.Id, id));

            if (texture.Id == null)
            {
                texture.Texture = new STexture.UTexture(Main.Instance.Content.Load<TextureCube>(Path.Content.Textures + id));
                texture.Id = id;
            }

            if (texture.Texture.Texture == null)
            {
                DebugHandler.Log("Texture with id \"" + id + "\" was not found.", DebugHandler.EDebugLevel.Warning);
                return null;
            }
            else if (texture.Texture.TextureType() != typeof(TextureCube))
            {
                DebugHandler.Log("Texture with id \"" + id + "\" is not of type Textue.", DebugHandler.EDebugLevel.Warning);
                return null;
            }

            _textures[group].Add(texture);

            return (TextureCube)texture.Texture.Texture;
        }
        #endregion

        /// <summary>
        /// Unloads a whole group or world
        /// </summary>
        /// <param name="group">The group or world to unload</param>
        /// <remarks>Group global cannot be unloaded</remarks>
        public void UnloadGroup(string group)
        {
            if (!Equals(group, "global") && group != null)
            {
                DebugHandler.Log("Unloading texture group \"" + group + "\"");
                _textures.Remove(group);
            }
        }
    }
}
